package cn.tedu.csmall.passport.security;

import cn.tedu.csmall.common.web.JsonResult;
import cn.tedu.csmall.common.web.State;
import com.alibaba.fastjson.JSON;
import io.jsonwebtoken.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collection;
import java.util.List;

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    @Value("${csmall.jwt.secret-key}")
    private String secretKey ;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 清除Spring Security上下文中的数据
        //避免此前曾经存入过用户信息，后续即使没有系带JWT,在Spring Security仍保存有上下文
        SecurityContextHolder.clearContext();
        //客户端提交请求时，必须在请求头的Authentication中添加JWT数据，这是当前服务器程序的规定
        //尝试获取JWT数据
        String jwt = request.getHeader("Authorization");
        System.out.println("从请求头中获取的JWT："+jwt);
        //判断是否存在jwt数据
        if(!StringUtils.hasText(jwt)){
            //不存在jwt数据，则放行，后续有其他过滤器及相关组键进行其他处理，例如未登录
            //此处不宜直接阻止进行，因为登录，注册等请求本应该没有jwt
            System.out.println("请求头中无JWT数据，当前过滤器将放行");
            filterChain.doFilter(request, response); // 继续执行过滤器链中后续的过滤器
            return; // 必须
        }
        // 注意：此时执行时，如果请求头中携带了Authentication，日志中将输出，且不会有任何响应，因为当前过滤器尚未放行
        // 以下代码有可能抛出异常
        // 密钥和哥哥key应该统一定义
        String username=null;
        String permissionsString = null;
        try{
            System.out.println("请求头中包含JWT，准备解析此数据……");
            Claims claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwt).getBody();
            username = claims.get(JwtConst.KEY_USERNAME).toString();
            permissionsString = claims.get(JwtConst.KEY_PERMISSIONS).toString();
            System.out.println("username=" + username);
            System.out.println("permissionsString=" + permissionsString);
        }catch (ExpiredJwtException e){
            System.out.println("解析JWT失败，此JWT已过期"+e.getMessage());
            JsonResult<Void> jsonResult = JsonResult.fail(
                    State.ERR_JWT_EXPIRED,"你的登录已过期，重新登录");
            String jsonString = JSON.toJSONString(jsonResult);
            System.out.println("响应结果："+jsonString);
            response.setContentType("application/json;charset=utf-8");
            response.getWriter().println(jsonString);
            return;
        }catch (MalformedJwtException e){
            System.out.println("解析JWT失败，此JWT数据错误，无法解析"+e.getMessage());
            JsonResult<Void> jsonResult = JsonResult.fail(
                    State.ERR_JWT_MALFORMED,"登录信息失败，重新登录");
            String jsonString = JSON.toJSONString(jsonResult);
            System.out.println("响应结果："+jsonString);
            response.setContentType("application/json;charset=utf-8");
            response.getWriter().println(jsonString);
            return;
        }catch (SignatureException e){
            System.out.println("解析JWT失败，此JWT签名错误"+e.getMessage());
            JsonResult<Void> jsonResult = JsonResult.fail(
                    State.ERR_JWT_SIGNATURE,"登录信息失败，重新登录");
            String jsonString = JSON.toJSONString(jsonResult);
            System.out.println("响应结果："+jsonString);
            response.setContentType("application/json;charset=utf-8");
            response.getWriter().println(jsonString);
            return;
        }catch (Exception e){
            System.out.println("解析JWT失败,异常类型"+e.getClass().getName());
            e.printStackTrace();
            JsonResult<Void> jsonResult = JsonResult.fail(
                    State.ERR_INTERNAL_SERVER_ERROR,"登录信息失败，重新登录");
            String jsonString = JSON.toJSONString(jsonResult);
            System.out.println("响应结果："+jsonString);
            response.setContentType("application/json;charset=utf-8");
            response.getWriter().println(jsonString);
            return;
        }
        // 将此前从JWT中读取到的permissionsString（JSON字符串）转换成Collection<? extends GrantedAuthority>
        List<SimpleGrantedAuthority> permissions
                = JSON.parseArray(permissionsString, SimpleGrantedAuthority.class);
        System.out.println("从JWT中获取到的权限转换成Spring Security要求的类型：" + permissions);

        //将解析得到的用户信息传递给Spring Security
        //获取Spring Security的上下文，并将Authentication放到上下文中
        //在Authentication中封装，用户名 null（密码） 权限列表
        //因为接下来并不会处理认证，所以Authentication中不需要密码
        //后续，Spring Security发现上下文中有Authentication时，会视为已登录，甚至可以获取i昂关系
        Authentication authentication = new UsernamePasswordAuthenticationToken(username,null,permissions);
        SecurityContextHolder.getContext().setAuthentication(authentication);
        // 放行
        filterChain.doFilter(request, response);
    }
}
